# encoding=utf-8
import sys
import tkinter as tk
from tkinter import ttk
from collections import OrderedDict
from data import Global
from module import WidgetTip, TopWinBase, TitleFrame, LabelCombobox, BaseButton, MultiSelector, TextEditor
from handle import AppController
from utils import Util


class Settings(TopWinBase):

    def pack(self, app):
        def choose_theme(*args):
            app.set_dark() if lcb_t.get() == '暗黑' else app.set_light()

        def choose_sort(*args):
            Global.time_sort = True if lcb_s.get() == '是' else False

        def choose_play(*args):
            Global.play_sound = True if lcb_p.get() == '是' else False

        self.topping()
        self.master.title('设置')
        self.resize(Global.app_width, Global.settings_height)
        self.set_position(Global.default_pos[0], Global.default_pos[1] + 200)
        lcb_t = LabelCombobox(self.master, '主界面主题色', ('暗黑', '明亮'), row=0, column=0)
        lcb_t.bind(choose_theme)
        lcb_t.current(0) if Global.current_theme[0] == 'dark' else lcb_t.current(1)
        lcb_s = LabelCombobox(self.master, "按'倒计时'排序", ('是', '否'), row=1, column=0)
        lcb_s.bind(choose_sort)
        lcb_s.current(0) if Global.time_sort else lcb_s.current(1)
        lcb_p = LabelCombobox(self.master, "开启提示音", ('是', '否'), row=2, column=0)
        lcb_p.bind(choose_play)
        lcb_p.current(0) if Global.play_sound else lcb_p.current(1)


class About(TopWinBase):

    def pack(self):
        self.topping()
        self.master.title('关于软件')
        self.resize(Global.app_width, Global.about_height)
        self.set_position(Global.default_pos[0], Global.default_pos[1] + 200)
        tk.Label(self.master, text='Bingo Todo Task', font=(Global.app_font, 16, 'bold'), fg='Red').pack()
        tk.Label(self.master, text=Global.about_info, width=60).pack()


class TodoDetail(TopWinBase):

    def __init__(self, controller, todo):
        self.controller = controller
        self.todo = todo
        self.todo_en = None
        self.todo_text = None
        self.rate_combo = None
        self.date_combo = None
        self.time_en = None
        self.task_en = None
        self.todo_prio_var = tk.IntVar()
        self.todo_md_var = tk.IntVar()
        self.clock_switch_var = tk.IntVar()
        self.task_switch_var = tk.IntVar()

    def get_todo_data(self, key):
        """
        解析和返回控件数据
        如果是点击新建，则返回默认控件数据
        如果是点击待办事项，则返回待办事项数据
        """
        # is_clock, rate_s, date_s, time_s, is_task, task, todo_prio, todo_md, todo_text
        total = Global.todo_data
        return {
            'todo_text': total[self.todo][8] if self.todo else '',
            'todo_md': total[self.todo][7] if self.todo else 0,
            'todo_prio': total[self.todo][6] if self.todo else 2,
            'is_clock': total[self.todo][0] if self.todo else 1,
            'rate': total[self.todo][1] if self.todo and total[self.todo][1] else '按日期',
            'date': total[self.todo][2] if self.todo and total[self.todo][2] else Util.get_today(),
            'time': total[self.todo][3] if self.todo and total[self.todo][3] else '',
            'is_task': total[self.todo][4] if self.todo else 0,
            'task': total[self.todo][5] if self.todo else ''
        }[key]

    def fill_data(self):
        """
        填充控件数据，如果是点击的待办事项，则使用对应待办数据填充
        """
        self.todo_en.insert('0', self.todo)
        self.todo_text.insert_text(self.get_todo_data('todo_text'))
        is_md = self.get_todo_data('todo_md')
        self.todo_md_var.set(is_md)
        self.show_markdown_todo()
        self.todo_prio_var.set(self.get_todo_data('todo_prio'))
        self.rate_combo.set(self.get_todo_data('rate'))
        self.date_combo.reset_entry(self.get_todo_data('date'))
        self.time_en.insert('0', self.get_todo_data('time'))
        is_clock = self.get_todo_data('is_clock')
        self.clock_switch_var.set(is_clock)
        self.show_clock_widgets()
        self.task_en.insert('0', self.get_todo_data('task'))
        is_task = self.get_todo_data('is_task')
        self.task_switch_var.set(is_task)
        self.show_task_widgets()

    def show_markdown_todo(self, *args):
        if self.todo_md_var.get():
            lines = self.todo_text.get_text()
            self.todo_text.clear()
            self.todo_text.show_markdown(lines)
            self.todo_text.configure({'state': 'disabled'})
        else:
            self.todo_text.configure({'state': 'normal'})
            lines = self.todo_text.get_text()
            self.todo_text.clear()
            self.todo_text.insert_text(lines)

    def show_markdown_eg(self, *args):
        def reshow(event=None):
            t2.configure({'state': 'normal'})
            t2.clear()
            t2.show_markdown(t1.get_text())
            t2.configure({'state': 'disabled'})

        self.topping(False)
        top = tk.Toplevel()
        top.title('伪Markdown示例')
        top.iconphoto(False, tk.PhotoImage(file='img/app.png'))
        top.wm_attributes('-topmost', 1)
        top.geometry('+%s+%s' % Global.default_pos)
        t1 = TextEditor(top, height=35, size=11)
        t1.pack(side='left')
        t1.insert_text(Global.todo_eg_lines)
        t1.text.bind('<Return>', reshow)
        t2 = TextEditor(top, height=35, size=11)
        t2.pack(side='left')
        t2.show_markdown(Global.todo_eg_lines)
        t2.configure({'state': 'disabled'})

    def show_clock_widgets(self, event=None):
        if self.clock_switch_var.get():
            self.rate_combo.configure({'state': 'normal'})
            self.rate_combo.set(self.get_todo_data('rate'))
            self.rate_combo.configure({'state': 'readonly'})
            self.date_combo.disable(False)
            self.update_date_values(False)
            self.time_en.configure({'state': 'normal'})
            self.time_en.delete('0', 'end')
            self.time_en.insert('0', self.get_todo_data('time'))
            self.task_en.configure({'state': 'normal'})
        else:
            self.rate_combo.configure({'state': 'normal'})
            self.rate_combo.set('')
            self.rate_combo.configure({'state': 'disabled'})
            self.date_combo.disable(True)
            self.time_en.delete('0', 'end')
            self.time_en.configure({'state': 'disabled'})
            self.task_switch_var.set(0)
            self.show_task_widgets()

    def show_task_widgets(self, event=None):
        if self.task_switch_var.get():
            self.task_en.grid(row=1, column=1)
            if self.clock_switch_var.get():
                self.task_en.configure({'state': 'normal'})
            else:
                self.task_en.configure({'state': 'disabled'})
        else:
            self.task_en.grid_forget()
            self.task_en.delete('0', 'end')

    def update_date_values(self, revalue=True):
        rate = self.rate_combo.get()
        values_map = {
            '': '',
            '按日期': Util.get_month_date(),
            '每天': ['每天'],
            '每周': Util.get_week_day(),
            '每月': Util.get_month_days()
        }
        self.date_combo.set_values(values_map[rate])
        if revalue:
            self.date_combo.current([0])
        else:
            self.date_combo.reset_entry(self.get_todo_data('date'))

    def pack_todo(self, todo_fm):
        for i, text in enumerate(('待办事项：', '详细内容：')):
            tk.Label(todo_fm, text=text, font=(Global.app_font, 10)).grid(row=i, column=0, padx=20, pady=5, sticky='nw')
        self.todo_en = ttk.Entry(todo_fm, width=60, font=(Global.app_font, 11))
        self.todo_en.grid(row=0, column=1, sticky='w')
        self.todo_text = TextEditor(todo_fm, size=11 if Global.is_windows else 10)
        self.todo_text.grid(row=1, column=1, sticky='w')
        md_fm = tk.Frame(todo_fm)
        md_fm.grid(row=2, column=1, sticky='w')
        tk.Checkbutton(md_fm, text='将详细内容渲染为伪Markdown格式（只读）', font=(Global.app_font, 9),
                       variable=self.todo_md_var, command=self.show_markdown_todo).pack(side='left')
        tk.Button(md_fm, text='伪Markdown示例?', font=(Global.app_font, 8), fg='Grey', bd=0,
                  command=self.show_markdown_eg).pack(side='left', padx=20)
        tk.Label(todo_fm, text='优先级：', font=(Global.app_font, 10)).grid(row=3, column=0, padx=20, pady=5, sticky='nw')
        prio_fm = tk.Frame(todo_fm)
        prio_fm.grid(row=3, column=1, sticky='w')
        for i, text in enumerate(('高', '中', '低')):
            tk.Radiobutton(prio_fm, text=text, font=(Global.app_font, 11, 'bold'), fg=Global.prio_map_fg[i],
                           variable=self.todo_prio_var, value=i).pack(side='left', padx=10)

    def pack_clock(self, clock_fm):
        tip = (
            '（设置待办提醒后，待办时间截止时会自动弹窗提醒）',
            '（可多选；当频次为‘按日期’时，只显示今后一个月内的日期）',
            '（可设置多个时间，空格隔开，例如：00:00 10:01 22:30）'
        )
        tk.Checkbutton(clock_fm, text='设置提醒', font=(Global.app_font, 10), variable=self.clock_switch_var,
                       command=self.show_clock_widgets).grid(row=0, column=0, padx=10)
        tk.Label(clock_fm, text=tip[0], font=(Global.app_font, 8), fg='Gray').grid(row=0, column=1, padx=10, sticky='w')
        tk.Label(clock_fm, text='频次', font=(Global.app_font, 10)).grid(row=1, column=0, padx=10, pady=5)
        self.rate_combo = ttk.Combobox(clock_fm, values=('按日期', '每天', '每周', '每月'), width=30)
        self.rate_combo.grid(row=1, column=1, sticky='w')
        self.rate_combo.bind('<<ComboboxSelected>>', lambda event: self.update_date_values())
        tk.Label(clock_fm, text='日期', font=(Global.app_font, 10)).grid(row=2, column=0, padx=10)
        self.date_combo = MultiSelector(clock_fm, values=Util.get_month_date(), width=60)
        self.date_combo.grid(row=2, column=1, sticky='w')
        tk.Label(clock_fm, text=tip[1], font=(Global.app_font, 8), fg='Gray').grid(row=3, column=1, padx=10, sticky='w')
        tk.Label(clock_fm, text='时间', font=(Global.app_font, 10)).grid(row=4, column=0, padx=10)
        self.time_en = ttk.Entry(clock_fm, width=60, font=(Global.app_font, 10))
        self.time_en.grid(row=4, column=1, sticky='w')
        tk.Label(clock_fm, text=tip[2], font=(Global.app_font, 8), fg='Gray').grid(row=5, column=1, padx=10, sticky='w')

    def pack_task(self, task_fm):
        tk.Checkbutton(task_fm, text='执行命令', font=(Global.app_font, 10), variable=self.task_switch_var,
                       command=self.show_task_widgets).grid(row=0, column=0, padx=10)
        tk.Label(task_fm, text='（设置待办的执行命令可以在待办时间截至时帮你自动完成某项工作）', font=(Global.app_font, 8),
                 fg='Gray').grid(row=0, column=1, padx=10, sticky='w')
        self.task_en = ttk.Entry(task_fm, width=60, font=(Global.app_font, 10))

    def pack(self):
        self.topping()
        self.set_position(*Global.default_pos)
        self.resize(Global.detail_width, Global.detail_height)
        self.master.title('修改待办' if self.todo else '新增待办')
        self.master.iconphoto(False, tk.PhotoImage(file='img/app.png'))
        todo_fm = TitleFrame(self.master, Global.detail_width, Global.detail_height - 300).master()
        clock_fm = TitleFrame(self.master, Global.detail_width, 200, outer=True).master()
        task_fm = TitleFrame(self.master, Global.detail_width, 50, outer=True).master()
        tk.Button(self.master, text='确认', width=30, bd=0, fg='Snow', bg='DodgerBlue3',
                  command=self.confirm).pack(pady=10)
        self.pack_todo(todo_fm)
        self.pack_clock(clock_fm)
        self.pack_task(task_fm)
        self.fill_data()

    def confirm(self, event=None):
        self.topping(False)
        todo_en = self.todo_en.get().strip()
        todo_text = self.todo_text.get_text().strip()
        todo_md = self.todo_md_var.get()
        todo_prio = self.todo_prio_var.get()
        is_clock = self.clock_switch_var.get()
        rate_s = self.rate_combo.get().strip()
        date_s = self.date_combo.get().strip()
        time_s = self.time_en.get().strip()
        is_task = self.task_switch_var.get()
        task = self.task_en.get()
        if not todo_en:
            WidgetTip.error("请输入待办事项!")
            return
        if is_clock:
            if not rate_s:
                WidgetTip.error("请选择频次!")
                return
            if not date_s:
                WidgetTip.error("请选择日期!")
                return
            if not time_s:
                WidgetTip.error("请输入时间!")
                return
            time_list, tt, invalid = [], None, True
            for tt in time_s.split():
                ft = Util.format_time(tt)
                if not ft:
                    break
                if ft in time_list:
                    WidgetTip.error("%s时间重复!" % tt)
                    return
                time_list.append(ft)
            else:
                invalid = False
            if invalid:
                WidgetTip.error("%s的时间格式不正确!" % tt)
                return
            time_s = ' '.join(Util.sort_time(time_list))

        if is_task and not task:
            WidgetTip.error("请输入命令!")
            return

        data = (is_clock, rate_s, date_s, time_s, is_task, task, todo_prio, todo_md, todo_text)
        if self.todo:
            if not self.controller.modify_todo_data(self.todo, todo_en, data):
                return
            self.close()
            WidgetTip.info("修改待办成功！")
        else:
            if not self.controller.init_todo_data(todo_en, data):
                return
            self.close()
            WidgetTip.info("新增待办成功！")


class App(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)
        self._children = OrderedDict()
        self.image_data = []
        self.fm_body = None
        self.fm_body_inst = OrderedDict()
        self.foot_instance = None
        self.controller = None
        self.detail_page = None
        self.tips_win = None
        self.tips_todo = []

    def set_theme(self, theme):
        if theme == 'dark':
            Global.current_theme = ('dark', Global.dark_bg, Global.dark_fg)
            bg = Global.dark_bg
            fg = Global.dark_fg
        else:
            Global.current_theme = ('light', Global.light_bg, Global.light_fg)
            bg = Global.light_bg
            fg = Global.light_fg
        for widget, area in self._children.items():
            if area == 'bg':
                widget.configure({'bg': bg})
            elif area == 'fg':
                widget.configure({'fg': fg})
            else:
                widget.configure({'bg': bg})
                widget.configure({'fg': fg})

    def set_light(self):
        self.set_theme('light')

    def set_dark(self):
        self.set_theme('dark')

    def set_position(self, x, y):
        self.geometry('+{}+{}'.format(x, y))

    def resize(self, width, height):
        pos_list = self.winfo_geometry().replace('x', '+').split('+')
        self.resizable(True, True)
        self.geometry("%sx%s+%s+%s" % (width, height, pos_list[2], int(pos_list[3]) - Global.window_height))
        self.resizable(False, False)

    def add_children(self, widget, area='bg'):
        if widget in self._children:
            return
        self._children[widget] = area

    def del_children(self, widget):
        if widget in self._children:
            del self._children[widget]

    def run(self):
        self.lazy_loading()
        self.mainloop()

    def close(self):
        if not WidgetTip.ask("确认退出？"):
            return
        Global.app_running = False
        self.destroy()
        sys.exit(0)

    def pack(self):
        self.withdraw()
        self.update()
        self.iconphoto(False, tk.PhotoImage(file='img/app.png'))
        self.title("Bingo Todo Task   v%s" % Global.app_version)
        self.protocol("WM_DELETE_WINDOW", self.close)
        self.geometry("%sx%s" % (Global.app_width, Global.app_height))
        self.resizable(False, False)
        self.attributes('-topmost', 1)
        self.image_data.append(tk.PhotoImage(file='img/add.png'))
        self.image_data.append(tk.PhotoImage(file='img/set.png'))
        self.image_data.append(tk.PhotoImage(file='img/info.png'))
        self.image_data.append(tk.PhotoImage(file='img/del.png'))
        master = TitleFrame(self, Global.app_width, Global.app_height).master()
        fm_head = TitleFrame(master, Global.app_width, Global.head_height).master()
        fm_body = TitleFrame(master, Global.app_width,
                             Global.app_height - Global.head_height - Global.foot_height, outer=True).master()
        fm_foot = TitleFrame(master, Global.app_width, Global.foot_height).master()
        self.add_children(master)
        self.add_children(fm_head)
        self.add_children(fm_body)
        self.add_children(fm_foot)
        self.pack_head(fm_head)
        self.pack_body(fm_body)
        self.pack_foot(fm_foot)
        self.fm_body = fm_body
        self.deiconify()

    def pack_head(self, fm_head):
        add_btn = BaseButton(fm_head, image=self.image_data[0], command=self.show_todo_detail)
        add_btn.pack(side='left', ipadx=20, ipady=5)
        abt_btn = BaseButton(fm_head, image=self.image_data[2], command=self.show_about)
        abt_btn.pack(side='right', padx=10)
        set_btn = BaseButton(fm_head, image=self.image_data[1], command=self.show_settings)
        set_btn.pack(side='right')
        self.add_children(add_btn)
        self.add_children(abt_btn)
        self.add_children(set_btn)

    def pack_body(self, fm_body):
        title_fm = TitleFrame(fm_body, Global.app_width, Global.title_height, outer=True).master()
        del_lab = tk.Label(title_fm, width=3)
        del_lab.grid(row=0, column=0)
        width = Global.todo_width + 16 if Global.is_windows else Global.todo_width + 6
        todo_lab = tk.Label(title_fm, text='待办事项', width=width, anchor='w')
        todo_lab.grid(row=0, column=1)
        time_lab = tk.Label(title_fm, text='倒计时', anchor='w')
        time_lab.grid(row=0, column=2)
        self.add_children(title_fm)
        self.add_children(del_lab)
        self.add_children(todo_lab, area='all')
        self.add_children(time_lab, area='all')

    def pack_foot(self, fm_foot):
        todo_lab = tk.Label(fm_foot, font=(Global.app_font, 9))
        todo_lab.pack(side='left', padx=10)
        date_lab = tk.Label(fm_foot, font=(Global.app_font, 9))
        date_lab.pack(side='right', padx=10)
        self.add_children(todo_lab, area='all')
        self.add_children(date_lab, area='all')
        self.foot_instance = (todo_lab, date_lab)

    def add_todo_tip_item(self, todo, color):
        def close(*args):
            self.tips_win.destroy()
            self.tips_win = None
            self.tips_todo = []

        if todo in self.tips_todo:
            return
        self.wm_attributes('-topmost', 0)
        self.deiconify()
        if not self.tips_win:
            self.tips_todo = []
            self.tips_win = tk.Toplevel()
            self.tips_win.transient(self)       # 去除主窗体的最小化和最大化按钮
            self.tips_win.withdraw()
            self.tips_win.protocol("WM_DELETE_WINDOW", close)
            self.tips_win.title('时间到！')

        self.tips_todo.append(todo)
        tk.Label(self.tips_win, text=todo, font=(Global.app_font, 13, 'bold'), fg=color, bg=Global.current_theme[1],
                 width=64).pack(fill='both', ipady=5)
        self.tips_win.geometry('+%s+%s' % (Global.default_pos[0], 300))
        self.tips_win.wm_attributes('-topmost', 1)
        self.tips_win.update()
        self.tips_win.deiconify()

    def show_about(self):
        self.wm_attributes('-topmost', 0)
        About().show()

    def show_settings(self):
        self.wm_attributes('-topmost', 0)
        Settings().show(self)

    def show_todo_detail(self, todo=''):
        self.wm_attributes('-topmost', 0)
        if self.detail_page:
            try:
                self.detail_page.close()
            except:
                pass
        self.detail_page = TodoDetail(self.controller, todo)
        self.detail_page.show()

    def refresh_app_height(self):
        Global.reset_app_height()
        self.resize(Global.app_width, Global.app_height)
        self.set_theme(Global.current_theme[0])

    '''
    def change_body_height(self, increase):
        add = Global.todo_height if increase else -Global.todo_height
        self.fm_body.config({'height': self.fm_body.cget('height') + add})
    '''

    def del_todo_item(self, todo):
        if not WidgetTip.ask("是否确认删除待办?\n%s" % todo):
            return
        for widget in self.fm_body_inst[todo]:
            self.del_children(widget)
            widget.destroy()
        del self.fm_body_inst[todo]
        self.controller.del_todo_data(todo)

    def add_todo_item(self, todo, color, time_str):
        fm_inst = TitleFrame(self.fm_body, Global.app_width, Global.todo_height)
        fm = fm_inst.master()
        del_btn = BaseButton(fm, image=self.image_data[3], command=lambda x=todo: self.del_todo_item(x))
        del_btn.grid(row=0, column=0)
        todo_btn = BaseButton(fm, text=todo, width=Global.todo_width, fg=color,
                              font=(Global.app_font, 12, 'bold'), anchor='w',
                              command=lambda x=todo: self.show_todo_detail(x))
        todo_btn.grid(row=0, column=1, padx=1, ipady=Global.todo_ipady)
        time_lab = tk.Label(fm, text=time_str, anchor='w')
        time_lab.grid(row=0, column=2)
        self.add_children(fm_inst)
        self.add_children(del_btn)
        self.add_children(todo_btn)
        self.add_children(time_lab, area='all')
        self.fm_body_inst[todo] = (fm_inst, del_btn, todo_btn, time_lab)

    def update_todo_item(self, todo, time_str, time_color, new_todo=None, new_color=None):
        if todo not in self.fm_body_inst:
            return
        self.fm_body_inst[todo][3].configure({'text': time_str, 'fg': time_color})
        if new_todo:
            instance = self.fm_body_inst[todo]
            todo_keys = list(self.fm_body_inst.keys())
            index = todo_keys.index(todo)
            del self.fm_body_inst[todo]
            self.fm_body_inst[new_todo] = instance
            self.fm_body_inst[new_todo][2].configure({'text': new_todo, 'fg': new_color})
            self.fm_body_inst[new_todo][2].update_command(lambda x=new_todo: self.show_todo_detail(x))
            self.fm_body_inst[new_todo][1].update_command(lambda x=new_todo: self.del_todo_item(x))
            # 此处控件实例实际还在当前位置，但是new_todo在fm_body_inst的最后，与实际不符。
            # 需要将控件中old_todo控件之后的实例均移动一次到fm_body_inst的最后。
            for todo in todo_keys[index + 1:]:
                self.fm_body_inst.move_to_end(todo)

    def update_foot_info(self, todo_txt='', date_txt=''):
        if not self.foot_instance:
            return
        if todo_txt:
            self.foot_instance[0].configure({'text': todo_txt})
        if date_txt:
            self.foot_instance[1].configure({'text': date_txt})

    def lazy_loading(self):
        self.controller = AppController(self)
        self.controller.start()


def main():
    app = App()
    app.set_position(*Global.default_pos)
    app.pack()
    app.set_dark()
    # app.set_light()
    app.run()


if __name__ == '__main__':
    main()
